package de.uni.muenster.idw2.location.bluetooth.service.fingerprinting.images;

import java.io.File;
import java.io.IOException;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;


/**
 * Measurements image.
 * 
 * Represents the maximum, average, and minimum signal strengths measurements 
 * which were taken on a specific floor for a specific dongle. 
 * 
 * @author Eva Peters
 *
 */
public class RGBMeasurementImage 
{		
	/**
	 * The name of the dongle.
	 */
	private String dongleName;
	
	/**
	 * The file name.
	 */
	private String filename;
	
	/**
	 * The width of the image.
	 */
	private int width;

	/**
	 * The height of the image.
	 */
	private int height;
	
	/**
	 * The bitmap image.
	 */
	private Bitmap bitmap;
	
	/**
	 * The range position image which contains the results of the position scan.
	 */
	private RangeArrayImage rangePositionImage;
	
	/**
	 * The range accuracy image which contains the results of the accuracy scan.
	 */
	private RangeArrayImage rangeAccuracyImage;
	
	private int seesionId = Integer.MIN_VALUE;
	
	/**
	 * Constructor.
	 * 
	 * @param dongleName The dongle name.
	 * @param fileName The file name.
	 * @throws IOException If the image cannot be read.
	 */
	public RGBMeasurementImage(String dongleName,
					    String fileName) throws IOException
	{
		this.dongleName = dongleName;
		this.filename = fileName;

		File directory = Environment.getExternalStorageDirectory();
		File file = new File(directory, fileName);
		
		this.bitmap = BitmapFactory.decodeFile(file.getPath());

		this.width = bitmap.getWidth();
		this.height = bitmap.getHeight();
		
		this.rangePositionImage = new RangeArrayImage(dongleName, width, height);
		this.rangeAccuracyImage = new RangeArrayImage(dongleName, width, height);
	}
	
	/**
	 * Scans the image in different direction for determining the position and accuracy.
	 *
	 * @param positiveMeanSignalStrength The positive mean signal strength.
	 * @param positiveMinimumSignalStrength The positive minimum signal strength.
	 * @param positiveMaximumSignalStrength The positive maximum signal strength.
	 * @throws IOException If the range image cannot be written.
	 */
	public void scan(int positiveMeanSignalStrength, int positiveMinimumSignalStrength, int positiveMaximumSignalStrength) throws IOException
	{
		scanWestEast(positiveMeanSignalStrength, positiveMinimumSignalStrength, positiveMaximumSignalStrength);
		scanNorthSouth(positiveMeanSignalStrength, positiveMinimumSignalStrength, positiveMaximumSignalStrength);
		scanSouthwestNortheast(positiveMeanSignalStrength, positiveMinimumSignalStrength, positiveMaximumSignalStrength);
		scanNorthwestSoutheast(positiveMeanSignalStrength, positiveMinimumSignalStrength, positiveMaximumSignalStrength);
	}
	
	/**
	 * Scans the channel from West to East (horizontally) for determining the position and accuracy.
	 * 
	 * @param positiveMeanSignalStrength The positive mean signal strength.
	 * @param positiveMinimumSignalStrength The positive minimum signal strength.
	 * @param positiveMaximumSignalStrength The positive maximum signal strength.
	 */
	private void scanWestEast(int positiveMeanSignalStrength, int positiveMinimumSignalStrength, int positiveMaximumSignalStrength)
	{
		for(int y = 0; y < height; y++)
		{
			for(int x = 0; x < width-1; x++)
			{
				int x_1 = x;
				int x_2 = x+1;
				
				scanPairAndPossiblyMarkPixels(x_1, y, x_2, y, positiveMeanSignalStrength, positiveMinimumSignalStrength, positiveMaximumSignalStrength);
			}
		}
	}

	/**
	 * Scans the channel from North to South (vertically) for determining the position and accuracy.
	 * 
	 * @param positiveMeanSignalStrength The positive mean signal strength.
	 * @param positiveMinimumSignalStrength The positive minimum signal strength.
	 * @param positiveMaximumSignalStrength The positive maximum signal strength.
	 */
	private void scanNorthSouth(int positiveMeanSignalStrength, int positiveMinimumSignalStrength, int positiveMaximumSignalStrength)
	{
		for(int x = 0; x < width; x++)
		{
			for(int y = 0; y < height-1; y++)
			{
				int y_1 = y;
				int y_2 = y+1;
				
				scanPairAndPossiblyMarkPixels(x, y_1, x, y_2, positiveMeanSignalStrength, positiveMinimumSignalStrength, positiveMaximumSignalStrength);
			}
		}
	}

	/**
	 * Scans the channel from Southwest to Northeast for determining the position and accuracy.
	 * 
	 * @param positiveMeanSignalStrength The positive mean signal strength.
	 * @param positiveMinimumSignalStrength The positive minimum signal strength.
	 * @param positiveMaximumSignalStrength The positive maximum signal strength.
	 */
	private void scanSouthwestNortheast(int positiveMeanSignalStrength, int positiveMinimumSignalStrength, int positiveMaximumSignalStrength)
	{
		for(int y = 1; y < height; y++)
		{
			for(int x = 0; x < width-1; x++)
			{
				int x_1 = x;
				int x_2 = x+1;
				int y_1 = y;
				int y_2 = y-1;
				scanPairAndPossiblyMarkPixels(x_1, y_1, x_2, y_2, positiveMeanSignalStrength, positiveMinimumSignalStrength, positiveMaximumSignalStrength);
			}
		}
	}

	/**
	 * Scans the channel from Northwest to Southeast for determining the position and accuracy.
	 * 
	 * @param positiveMeanSignalStrength The positive mean signal strength.
	 * @param positiveMinimumSignalStrength The positive minimum signal strength.
	 * @param positiveMaximumSignalStrength The positive maximum signal strength.
	 */
	private void scanNorthwestSoutheast(int positiveMeanSignalStrength, int positiveMinimumSignalStrength, int positiveMaximumSignalStrength)
	{
		for(int y = 0; y < height-1; y++)
		{
			for(int x = 0; x < width-1; x++)
			{
				int x_1 = x;
				int x_2 = x+1;
				int y_1 = y;
				int y_2 = y+1;
				scanPairAndPossiblyMarkPixels(x_1, y_1, x_2, y_2, positiveMeanSignalStrength, positiveMinimumSignalStrength, positiveMaximumSignalStrength);
			}
		}
	}

	/**
	 * Scans two pixels in the channel for determining the position and accuracy.
	 * 
	 * @param x_1 X position of the first pixel (to the right).
	 * @param y_1 Y position of the first pixel (down).
	 * @param x_2 X position of the second pixel (to the right).
	 * @param y_2 Y position of the second pixel (down).
	 * @param positiveMeanSignalStrength The positive mean signal strength.
	 * @param positiveMinimumSignalStrength The positive minimum signal strength.
	 * @param positiveMaximumSignalStrength The positive maximum signal strength.
	 */
	private void scanPairAndPossiblyMarkPixels(int x_1, 
					      int y_1, 
					      int x_2, 
					      int y_2, 
					      int positiveMeanSignalStrength, 
					      int positiveMinimumSignalStrength, 
					      int positiveMaximumSignalStrength)
	{
		//Position
		scanPairForPositionAndPossibleMarkPixels(x_1, y_1, x_2, y_2,
				positiveMeanSignalStrength);
		
		//Accuracy
		scanPairForAccuracyAndPossiblyMarkPixels(x_1, y_1, x_2, y_2,
				positiveMinimumSignalStrength, positiveMaximumSignalStrength);
	}

	/**
	 * Scans two pixels in the channel for determining the position.
	 *	
	 * All pixels are marked 
	 * whose value is the positive signal strength
	 * or whose value is next to it.
	 * 
	 * @param x_1 X position of the first pixel (to the right).
	 * @param y_1 Y position of the first pixel (down).
	 * @param x_2 X position of the second pixel (to the right).
	 * @param y_2 Y position of the second pixel (down).
	 * @param positiveMeanSignalStrength The positive mean signal strength.
	 */
	private void scanPairForPositionAndPossibleMarkPixels(int x_1, 
														  int y_1,
														  int x_2, 
														  int y_2, 
														  int positiveMeanSignalStrength) 
	{
		int positiveSignalStrength_Green_1 = readChannelPixelValue(x_1, y_1, EnumChannelType.MEAN_GREEN);
		if(positiveSignalStrength_Green_1 == positiveMeanSignalStrength)
		{
			markPixelInRangePositionImage(x_1, y_1);
		}
		
		int positiveSignalStrength_Green_2 = readChannelPixelValue(x_2, y_2, EnumChannelType.MEAN_GREEN);
		if(positiveSignalStrength_Green_2 == positiveMeanSignalStrength)
		{
			markPixelInRangePositionImage(x_2, y_2);
		}
		
		if((positiveSignalStrength_Green_1 > positiveMeanSignalStrength &&
		    positiveSignalStrength_Green_2 < positiveMeanSignalStrength) ||
		   (positiveSignalStrength_Green_1 < positiveMeanSignalStrength &&
		    positiveSignalStrength_Green_2 > positiveMeanSignalStrength))
		{
			markPixelInRangePositionImage(x_1, y_1);
			markPixelInRangePositionImage(x_2, y_2);
		}
	}

	/**
	 * Scans two pixels in the channel for determining the accuracy.
	 * 
	 * 1) Check if which pixels can be marked in the red (minimum) channel:
	 *    - A red pixels can be marked if its value is <= the positive maximum signal strength.
	 *    - Both red pixels can be marked if the positive maximum signal strength is between both values.
	 *    
	 * 2) Check if which pixels can be marked in the blue (maximum) channel:
	 *    - A blue pixels can be marked if its value is >= the positive maximum signal strength.
	 *    - Both blue pixels can be marked if the positive maximum signal strength is between both values.
	 *    
	 * 3) Mark only these pixels that can be marked in both channels.
	 *	
	 * @param x_1 X position of the first pixel (to the right).
	 * @param y_1 Y position of the first pixel (down).
	 * @param x_2 X position of the second pixel (to the right).
	 * @param y_2 Y position of the second pixel (down).
	 * @param positiveMinimumSignalStrength The positive minimum signal strength.
	 * @param positiveMaximumSignalStrength The positive maximum signal strength.
	 */
	private void scanPairForAccuracyAndPossiblyMarkPixels(int x_1, 
														  int y_1,
														  int x_2, 
														  int y_2, 
														  int positiveMinimumSignalStrength,
														  int positiveMaximumSignalStrength) 
	{
		//Red
		boolean mark_Red_1 = false;
		int positiveSignalStrength_Red_1 = readChannelPixelValue(x_1, y_1, EnumChannelType.MINIMUM_RED);
		if(positiveSignalStrength_Red_1 <= positiveMaximumSignalStrength)
		{
			mark_Red_1 = true;
		}
		
		boolean mark_Red_2 = false;
		int positiveSignalStrength_Red_2 = readChannelPixelValue(x_2, y_2, EnumChannelType.MINIMUM_RED);
		if(positiveSignalStrength_Red_2 <= positiveMaximumSignalStrength)
		{
			mark_Red_2 = true;
		}
		
		if((positiveSignalStrength_Red_1 > positiveMaximumSignalStrength &&
				positiveSignalStrength_Red_2 < positiveMaximumSignalStrength) ||
			   (positiveSignalStrength_Red_1 < positiveMaximumSignalStrength &&
					   positiveSignalStrength_Red_2 > positiveMaximumSignalStrength))
		{
			mark_Red_1 = true;
			mark_Red_2 = true;
		}
		
		//Blue
		boolean mark_Blue_1 = false;
		int positiveSignalStrength_Blue_1 = readChannelPixelValue(x_1, y_1, EnumChannelType.MAXIMUM_BLUE);
		if(positiveSignalStrength_Blue_1 >= positiveMinimumSignalStrength)
		{
			mark_Blue_1 = true;
		}
		
		boolean mark_Blue_2 = false;
		int positiveSignalStrength_Blue_2 = readChannelPixelValue(x_2, y_2, EnumChannelType.MAXIMUM_BLUE);
		if(positiveSignalStrength_Blue_2 >= positiveMinimumSignalStrength)
		{
			mark_Blue_2 = true;
		}
		
		if((positiveSignalStrength_Blue_1 > positiveMinimumSignalStrength &&
				positiveSignalStrength_Blue_2 < positiveMinimumSignalStrength) ||
			   (positiveSignalStrength_Blue_1 < positiveMinimumSignalStrength &&
					   positiveSignalStrength_Blue_2 > positiveMinimumSignalStrength))
		{
			mark_Blue_1 = true;
			mark_Blue_2 = true;
		}
		
		//Summary
		if(mark_Red_1 && mark_Blue_1)
		{
			markPixelInRangeAccuracyImage(x_1, y_1);
		}
		if(mark_Red_2 && mark_Blue_2)
		{
			markPixelInRangeAccuracyImage(x_2, y_2);
		}
	}

	/**
	 * Reads the pixel value of the channel.
	 * 
	 * @param x X position (to the right).
	 * @param y Y position (down).
	 * @param enumChannelType The channel type.
	 */
	private int readChannelPixelValue(int x, int y, EnumChannelType enumChannelType)
	{
		int pixelValue = readPixel(x, y);
		int channelPixelValue = 255; //initialize
		if(enumChannelType == EnumChannelType.MINIMUM_RED)
		{
			//red
			channelPixelValue = (pixelValue >> 16) & 0xff;
		}
		else if(enumChannelType == EnumChannelType.MEAN_GREEN)
		{
			//green
			channelPixelValue = (pixelValue >> 8) & 0xff;
		}
		else if(enumChannelType == EnumChannelType.MAXIMUM_BLUE)
		{
			//blue
			channelPixelValue = (pixelValue) & 0xff;
		}
		else
		{
			throw new RuntimeException("Unimplemented channel type '" + enumChannelType + "'.");
		}
		
		return channelPixelValue;
	}
	
	/**
	 * Reads the pixel value.
	 * 
	 * Returns an integer pixel in the default RGB color model (TYPE_INT_ARGB) 
	 * and default sRGB color space.
	 * 
	 * @param x X position (to the right).
	 * @param y Y position (down).
	 */
	private int readPixel(int x, int y)
	{
		int pixel = bitmap.getPixel(x, y);
		return pixel;
	}
	
	/**
	 * Marks the pixel in the range position image.
	 * 
	 * @param x X position (to the right).
	 * @param y Y position (down).
	 */
	private void markPixelInRangePositionImage(int x, int y)
	{
		rangePositionImage.markPixel(x, y);
	}
	
	/**
	 * Marks the pixel in the range accuracy image.
	 * 
	 * @param x X position (to the right).
	 * @param y Y position (down).
	 */
	private void markPixelInRangeAccuracyImage(int x, int y)
	{
		rangeAccuracyImage.markPixel(x, y);
	}
	
	/**
	 * Gets the dongle name.
	 * 
	 * @return The dongle name.
	 */
	public String getDongleName()
	{
		return dongleName;
	}
	
	/**
	 * Gets the range position image which contains the results of the scan.
	 * 
	 * @return The range image.
	 */
	public RangeArrayImage getRangePositionImage()
	{
		return rangePositionImage;
	}
	
	/**
	 * Gets the range accuracy image which contains the results of the scan.
	 * 
	 * @return The range image.
	 */
	public RangeArrayImage getRangeAccuracyImage()
	{
		return rangeAccuracyImage;
	}
	
	/**
	 * Gets the width of the image.
	 * 
	 * @return The width of the image.
	 */
	public int getWidth() 
	{
		return width;
	}

	/**
	 * Gets the height of the image.
	 * 
	 * @return The height of the image.
	 */
	public int getHeight() 
	{
		return height;
	}
	
	/**
	 * Gets the file name of the image.
	 * 
	 * @return The file name of the image.
	 */
	public String getFilename() 
	{
		return filename;
	}
	
	public void resetRangeImages(){
		rangeAccuracyImage.unmarkAllPixels();
		rangePositionImage.unmarkAllPixels();
	}
}
